#pragma once
#include <cmath>
#include <GL/glew.h>
#include <iostream>

namespace cagd
{
    class DCoordinate
    {
    public:
        GLdouble x, y, z;

        // default constructor
        DCoordinate(): x(0.0), y(0.0), z(0.0){};

        // special constructor
        DCoordinate(GLdouble x, GLdouble y, GLdouble z = 0.0): x(x), y(y), z(z){};

	// copy of the coordinates x, y, or z 
        inline GLdouble operator[](GLint rhs) const;

	// reference to the coordinates x, y, or z 
        inline GLdouble& operator[](GLint rhs);

        // add
        inline DCoordinate operator +(const DCoordinate &rhs) const;

        // add to this
        inline DCoordinate& operator +=(const DCoordinate &rhs);

        // subtract
        inline DCoordinate operator -(const DCoordinate &rhs) const;

        // subtract from this
        inline DCoordinate& operator -=(const DCoordinate &rhs);

        // dot product
        inline GLdouble operator *(const DCoordinate &rhs) const;

        // cross product
        inline DCoordinate operator %(const DCoordinate &rhs) const;

        // cross product with this
        inline DCoordinate& operator %=(const DCoordinate &rhs);

        // multiplicate with scalar from right
        inline DCoordinate operator *(GLdouble rhs) const;

        // multiplicate this with a scalar
        inline DCoordinate& operator *=(GLdouble rhs);

        // divide with scalar
        inline DCoordinate operator /(GLdouble rhs) const;

        // divide this with a scalar
        inline DCoordinate& operator /=(GLdouble rhs);

        inline GLdouble Magnitude() const;

        inline DCoordinate& Normalize();
    };

    inline DCoordinate DCoordinate::operator +(const DCoordinate &rhs) const
    {
        return DCoordinate(x + rhs.x, y + rhs.y, z + rhs.z);
    }
    inline DCoordinate& DCoordinate::operator +=(const DCoordinate &rhs)
    {
        x += rhs.x, y += rhs.y, z += rhs.z;
        return *this;
    }

    inline DCoordinate DCoordinate::operator -(const DCoordinate &rhs) const
    {
        return DCoordinate(x - rhs.x, y - rhs.y, z - rhs.z);
    }
    inline DCoordinate& DCoordinate::operator -=(const DCoordinate &rhs)
    {
        x -= rhs.x, y -= rhs.y, z -= rhs.z;
        return *this;
    }

    inline GLdouble DCoordinate::operator *(const DCoordinate &rhs) const
    {
        return x * rhs.x + y * rhs.y + z * rhs.z;
    }

    inline DCoordinate DCoordinate::operator %(const DCoordinate& rhs) const
    {
        return DCoordinate(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x);
    }
    inline DCoordinate& DCoordinate::operator %=(const DCoordinate& rhs)
    {
        GLdouble
                new_x = y * rhs.z - z * rhs.y,
                new_y = z * rhs.x - x * rhs.z,
                new_z = x * rhs.y - y * rhs.x;
        x = new_x, y = new_y, z = new_z;
        return *this;
    }

    inline DCoordinate DCoordinate::operator *(GLdouble rhs) const
    {
        return DCoordinate(x * rhs, y * rhs, z * rhs);
    }
    inline DCoordinate& DCoordinate::operator *=(GLdouble rhs)
    {
        x *= rhs, y *= rhs, z *= rhs;
        return *this;
    }

    inline DCoordinate DCoordinate::operator /(GLdouble rhs) const
    {
        return DCoordinate(x / rhs, y / rhs, z / rhs);
    }
    inline DCoordinate& DCoordinate::operator /=(GLdouble rhs)
    {
        x /= rhs, y /= rhs, z /= rhs;
        return *this;
    }

    inline GLdouble DCoordinate::operator [](GLint rhs) const
    {
        switch (rhs)
        {
        case 0:
           return x;
        case 1:
            return y;
        }
        return z;
    }
    inline GLdouble& DCoordinate::operator [](GLint rhs)
    {
        switch (rhs)
        {
        case 0:
           return x;
        case 1:
            return y;
        }
        return z;
    }

    inline GLdouble DCoordinate::Magnitude() const
    {
        return sqrt(x * x + y * y + z * z);
    }

    inline DCoordinate& DCoordinate::Normalize()
    {
        GLdouble length = Magnitude();
        if (length && length != 1.0)
        {
            x /= length;
            y /= length;
            z /= length;
        }
        return *this;
    }

    // scale from left with a scalar
    inline DCoordinate operator *(GLdouble lhs, const DCoordinate &rhs)
    {
        return DCoordinate(rhs.x * lhs, rhs.y * lhs, rhs.z * lhs);
    }
    // output to stream
    inline std::ostream& operator <<(std::ostream& lhs, const DCoordinate &rhs)
    {
        return lhs << rhs.x << " " << rhs.y << " " << rhs.z;
    }
    // input from stream
    inline std::istream& operator >>(std::istream& lhs, DCoordinate &rhs)
    {
        return lhs >> rhs.x >> rhs.y >> rhs.z;
    }
}
